package com.gitee.feizns.web.dao.ar;

import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.enums.SqlMethod;
import com.baomidou.mybatisplus.mapper.Condition;
import com.baomidou.mybatisplus.mapper.SqlHelper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.gitee.feizns.ArrayUtils;
import com.gitee.feizns.StreamUtils;
import com.gitee.feizns.StringUtils;
import com.gitee.feizns.bean.Getter;
import com.gitee.feizns.bean.PropertyUtils;
import com.gitee.feizns.reflect.FieldUtils;
import com.gitee.feizns.reflect.LambdaUtils;
import com.gitee.feizns.web.domain.page.PageAo;
import com.gitee.feizns.web.domain.page.PageVoRet;
import com.gitee.feizns.web.utils.ModelUtils;
import com.gitee.feizns.web.utils.PageUtils;
import lombok.SneakyThrows;
import org.apache.ibatis.session.SqlSession;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author feizns
 * @since 2019/7/19
 */
public abstract class AbstractModel<E extends AbstractModel<?>> extends Model<E> {

    private static final String DEFAULT_ID_PROP_NAME = "id";

    @Override
    protected Serializable pkVal() {
        Field field = pkField();
        return field != null ? FieldUtils.getData(this, field.getName()) : null;
    }

    /**
     * 主键字段
     * @return
     */
    public Field pkField() {
        Field pkField = FieldUtils.get(this.getClass(), field -> field.isAnnotationPresent(TableId.class));
        return pkField != null ? pkField : FieldUtils.get(this, DEFAULT_ID_PROP_NAME);
    }



    /**
     * 根据主键 selectObjByPk();
     * @param getter
     * @return
     */
    public <T> T selectObjByPk(Getter<E, T> getter, Serializable pk) {
        return selectObj(getter, (Wrapper<T>) Condition.create().eq(ModelUtils.underlinePkName(this), pk));
    }

    /**
     * 自己作为条件
     * @param getter
     * @return
     */
    public <T> T selectObj(Getter<E, T> getter) {
        return selectObj(getter, this);
    }

    /**
     * 指定条件对象
     * @param getter
     * @return
     */
    public <T> T selectObj(Getter<E, T> getter, Object condition) {
        return selectObj(getter, ModelUtils.conditionDefaultEq(condition));
    }

    /**
     * 指定条件单值.
     * @param getter
     * @param wrapper
     * @return
     */
    public <T> T selectObj(Getter<E, T> getter, Wrapper<T> wrapper) {
        String prop = StringUtils.toUnderline(PropertyUtils.getName(LambdaUtils.getImplMethodName(getter)));
        E one = selectOne(wrapper.setSqlSelect(prop));
        return one != null ? getter.getter(one) : null;
    }

    /**
     * 指定条件单值.
     * @param getter
     * @param sqlWhere
     * @param params
     * @return
     */
    public <T> T selectObj(Getter<E, T> getter, String sqlWhere, Object... params) {
        return selectObj(getter, (Wrapper<T>) Condition.create().where(sqlWhere, params));
    }



    /**
     * 指定条件单值.
     * @param field
     * @return
     */
    public <T> Object selectObjByPk(String field, Serializable pk) {
        return selectObj(field, Condition.create().eq(ModelUtils.underlinePkName(this), pk));
    }

    /**
     * 指定条件单值.
     * @param field
     * @return
     */
    public <T> Object selectObj(String field) {
        return selectObj(field, ModelUtils.conditionDefaultEq(this));
    }

    /**
     * 指定条件单值.
     * @param field
     * @param wrapper
     * @return
     */
    public <T> Object selectObj(String field, Wrapper<T> wrapper) {
        Map<String, Object> map = new HashMap<>();
        if ( SqlHelper.isEmptyOfWrapper(wrapper) )
            wrapper = Condition.create();

        wrapper.setSqlSelect(field);
        map.put("ew", wrapper);
        SqlSession sqlSession = sqlSession();

        try {
            return sqlSession.selectOne(sqlStatement(SqlMethod.SELECT_OBJS), map);
        }finally {
            closeSqlSession(sqlSession);
        }
    }

    /**
     * 指定条件单值.
     * @param field
     * @param sqlWhere
     * @param params
     * @return
     */
    public Object selectObj(String field, String sqlWhere, Object... params) {
        return selectObj(field, Condition.create().where(sqlWhere, params));
    }



    /**
     * 主键获取指定值
     * @param getter
     * @param <T>
     * @return
     */
    public <T> List<T> selectObsByPks(Getter<E, T> getter, Serializable... pks) {
        if ( pks != null ) {
            String pkName = ModelUtils.underlinePkName(this);
            if ( pks.length == 1 )
                return selectObs(getter, (Wrapper<T>) Condition.create().eq(pkName, pks[0]));
            else
                return selectObs(getter, (Wrapper<T>) Condition.create().in(pkName, (Object[]) pks));
        }
        return Collections.emptyList();
    }

    /**
     * 自己作为条件
     * @param getter
     * @param <T>
     * @return
     */
    public <T> List<T> selectObs(Getter<E, T> getter) {
        return selectObs(getter, ModelUtils.conditionDefaultEq(this));
    }

    /**
     * 单值列表
     * @param getter
     * @param wrapper
     * @return
     */
    public <T> List<T> selectObs(Getter<E, T> getter, Wrapper<T> wrapper) {
        String name = StringUtils.toUnderline(PropertyUtils.getName(LambdaUtils.getImplMethodName(getter)));
        List<E> es = selectList(wrapper.setSqlSelect(name));
        return es.stream().filter(Objects::nonNull).map(f -> getter.getter(f)).collect(Collectors.toList());
    }

    /**
     * 单值列表
     * @param getter
     * @param sqlWhere
     * @param params
     * @return
     */
    public <T> List<T> selectObs(Getter<E, T> getter, String sqlWhere, Object... params) {
        return selectObs(getter, Condition.create().where(sqlWhere, params));
    }

    /**
     * 根据主键查询映射到指定的类型
     * @param targetType
     * @param <T>
     * @return
     */
    public <T> T selectByPk(Class<T> targetType) {
        return selectByPk(targetType, pkVal());
    }

    /**
     * 根据主键查询映射到指定类型
     * @param targetType
     * @param <T>
     * @return
     */
    public <T> T selectByPk(Class<T> targetType, Serializable val) {
        Object one = selectOne(targetType, Condition.create().eq(ModelUtils.underlinePkName(this), val));
        return ModelUtils.mapVo(targetType, one);
    }

    /**
     * 批量编号
     * @return
     */
    public List<E> selectByPks(Serializable... ids) {
        if ( ArrayUtils.isNotEmpty(ids) ) {
            String pkName = ModelUtils.underlinePkName(this);
            Wrapper wrapper = Condition.create();
            if ( ids.length == 1 )
                wrapper.eq(pkName, ids[0]);
            else
                wrapper.in(pkName, (Object[]) ids);
            return selectList(wrapper);
        }
        return Collections.emptyList();
    }

    /**
     * 批量编号并映射
     * @param targetType
     * @param <T>
     * @return
     */
    public <T> List<T> selectByPks(Class<T> targetType, Serializable... ids) {
        return map(targetType, selectByPks(ids), Objects::nonNull);
    }



    /**
     * 自己作为条件
     * @return
     */
    public E selectOne() {
        return selectOne(this);
    }

    /**
     * 指定条件对象
     * @param condition
     * @return
     */
    public E selectOne(Object condition) {
        return selectOne(ModelUtils.conditionDefaultEq(condition));
    }

    /**
     * 单条记映射到指定类型：自己作为条件
     * @param targetType
     * @param <T>
     * @return
     */
    public <T> T selectOne(Class<T> targetType) {
        return selectOne(targetType, this);
    }

    /**
     * 单条记映射到指定类型：指定条件对象
     * @param targetType
     * @param <T>
     * @return
     */
    public <T> T selectOne(Class<T> targetType, Object condition) {
        return selectOne(targetType, ModelUtils.conditionDefaultEq(condition));
    }

    /**
     * 单条记映射到指定类型
     * @param targetType
     * @param wrapper
     * @param <T>
     * @return
     */
    public <T> T selectOne(Class<T> targetType, Wrapper<T> wrapper) {
        List<T> ts = selectList(targetType, wrapper);
        return ts.isEmpty() ? null : ts.get(0);
    }

    /**
     * 单条记录映射到指定类型
     * @param targetType
     * @param sqlWhere
     * @param params
     * @param <T>
     * @return
     */
    public <T> T selectOne(Class<T> targetType, String sqlWhere, Object... params) {
        return selectOne(targetType, (Wrapper<T>) Condition.create().where(sqlWhere, params));
    }



    /**
     * 自己作为条件
     * @return
     */
    public List<E> selectList() {
        return selectList(this);
    }

    /**
     * 指定条件对象
     * @return
     */
    public List<E> selectList(Object condition) {
        return selectList(ModelUtils.condition(condition));
    }

    /**
     * 自己作为条件
     * @param targetType
     * @param <T>
     * @return
     */
    public <T> List<T> selectList(Class<T> targetType) {
        return selectList(targetType, this);
    }

    /**
     * 指定条件对象
     * @param targetType
     * @param condition
     * @param <T>
     * @return
     */
    public <T> List<T> selectList(Class<T> targetType, Object condition) {
        return selectList(targetType, ModelUtils.conditionDefaultEq(condition));
    }

    /**
     * 目标类型为所需列
     * @param targetType
     * @param wrapper
     * @param <T>
     * @return
     */
    public <T> List<T> selectList(Class<T> targetType, Wrapper wrapper) {
        Map<String, Object> map = new HashMap<>();
        if ( SqlHelper.isEmptyOfWrapper(wrapper) )
            wrapper = Condition.create();

        setSelect(wrapper, targetType);
        map.put("ew", wrapper);
        SqlSession sqlSession = sqlSession();
        try {
            List<E> tl = sqlSession.selectList(sqlStatement(SqlMethod.SELECT_MAPS), map);
            return map(targetType, tl, Objects::nonNull);
        }finally {
            closeSqlSession(sqlSession);
        }
    }

    /**
     * @see AbstractModel#selectList(java.lang.Class, com.baomidou.mybatisplus.mapper.Wrapper)
     * @param targetType
     * @param sqlWhere
     * @param params
     * @param <T>
     * @return
     */
    public <T> List<T> selectList(Class<T> targetType, String sqlWhere, Object... params) {
        return selectList(targetType, Condition.create().where(sqlWhere, params));
    }

    /**
     * 直接作为条件
     * @param targetType
     * @param <T>
     * @return
     */
    public <T> List<T> selectAll(Class<T> targetType) {
        return selectList(targetType, this);
    }

    /**
     * 指定条件对象
     * @param targetType
     * @param <T>
     * @return
     */
    public <T> List<T> selectAll(Class<T> targetType, Object condition) {
        return selectList(targetType, ModelUtils.condition(condition));
    }

    /**
     * 自己作为条件
     * @param page
     * @return
     */
    public <T> PageVoRet<E> selectPage(PageAo page) {
        return selectPage(page, this);
    }


    /**
     * 指定对象作为条件
     * @param page
     * @return
     */
    public PageVoRet<E> selectPage(PageAo page, Object condition) {
        return selectPage(page, ModelUtils.conditionDefaultEq(condition));
    }

    /**
     * <p>
     * 翻页查询
     * </p>
     *
     * @param page    翻页查询条件
     * @param wrapper
     * @return
     */
    public <T> PageVoRet<E> selectPage(PageAo page, Wrapper<T> wrapper) {
        return (PageVoRet<E>) selectPage(this.getClass(), page, wrapper);
    }



    /**
     * <p>
     * 翻页查询
     * </p>
     *
     * @param page    翻页查询条件
     * @param wrapper
     * @return
     */
    public <T, R> PageVoRet<R> selectPage(Class<R> targetType, PageAo page, Wrapper<T> wrapper) {
        Map<String, Object> map = new HashMap<>();
        if ( SqlHelper.isEmptyOfWrapper(wrapper) )
            wrapper = Condition.create();

        setSelect(wrapper, targetType);
        map.put("ew", wrapper);
        SqlSession sqlSession = sqlSession();
        try {
            List<E> tl = sqlSession.selectList(sqlStatement(SqlMethod.SELECT_MAPS_PAGE), map, page.toRowBounds());
            return PageUtils.create(page, map(targetType, tl, StreamUtils::allTrue));
        }finally {
            closeSqlSession(sqlSession);
        }
    }

    /**
     * 自己作为条件
     * @param targetType
     * @param ao
     * @return
     */
    public <T> PageVoRet<T> selectPage(Class<T> targetType, PageAo ao) {
        return selectPage(targetType, ao, ModelUtils.conditionDefaultEq(this));
    }

    /**
     * 指定对象作为条件
     * @param targetType
     * @param ao
     * @param condition
     * @return
     */
    public <T> PageVoRet<T> selectPage(Class<T> targetType, PageAo ao, Object condition) {
        return selectPage(targetType, ao, ModelUtils.conditionDefaultEq(condition));
    }

    /**
     * 自身作为条件
     * @return
     */
    public boolean exists() {
        return exists(this);
    }

    /**
     * 根据主键判断是否存在
     * @return
     */
    public boolean existsPk() {
        return existsPk(pkVal());
    }


    /**
     * 根据主键判断是否存在
     * @return
     */
    public boolean existsPk(Serializable pkVal) {
        return exists(ModelUtils.underlinePkName(this), pkVal);
    }

    /**
     * 自身作为条件
     * @return
     */
    public boolean exists(Object condition) {
        return selectCount(ModelUtils.conditionDefaultEq(condition)) > 0;
    }

    /**
     * 自身作为添加的非项
     * @return
     */
    public boolean notExists() {
        return !exists();
    }

    /**
     * 不存在指定的主键
     * @return
     */
    public boolean notExistsPk() {
        return !existsPk();
    }

    /**
     * 自身作为添加的非项
     * @return
     */
    public boolean notExists(Object condition) {
        return !exists(condition);
    }

    /**
     * 简单的
     * @param field
     * @param val
     * @return
     */
    public boolean exists(String field, Object val) {
        return selectCount(String.format("%s = {0}", field), val) > 0;
    }

    /**
     * 条件构造器
     * @param wrapper
     * @return
     */
    public boolean exists(Wrapper wrapper) {
        return selectCount(wrapper) > 0;
    }

    /**
     * 添加数据并获取主键
     * @return
     */
    public Serializable create() {
        this.insert();
        return pkVal();
    }

    /**
     * 修改数据并获取主键
     * @param pk
     * @return
     */
    @SneakyThrows
    public Serializable updateByPk(Serializable pk) {
        Field field = pkField();
        field.setAccessible(true);
        field.set(this, pk);
        updateById();
        return pkVal();
    }

    /**
     * 将结果映射到目标类型
     * @param targetType
     * @param es
     * @return
     */
    private <T> List<T> map(Class<T> targetType, List<? extends Object> es, Predicate<T> filter) {
        return es.stream()
                .map(o -> ModelUtils.mapVo(targetType, o))
                .filter(filter).collect(Collectors.toList());
    }

    /**
     * 填充所需查询的字段
     * @param wrapper
     * @param type
     * @return
     */
    private <T> Wrapper<T> setSelect(Wrapper<T> wrapper, Class<?> type) {
        return wrapper.setSqlSelect(ModelUtils.createUnderlineAsHumpColumns(type));
    }

}
